"""
TypeScript Compiler API bridge.

Python wrapper for TypeScript Compiler API via Node.js subprocess communication.
Provides TypeScript AST parsing, type checking, and module resolution.
"""

import json
import subprocess
import os
import tempfile
import threading
import time
from typing import Dict, Any, List, Optional, Union
from pathlib import Path

from ...core.error_handler import AnalysisError, log_info, log_debug


class TypeScriptCompiler:
    """Python bridge to TypeScript Compiler API."""
    
    def __init__(self, node_modules_path: Optional[str] = None, timeout: int = 30):
        """Initialize TypeScript compiler bridge."""
        self.node_modules_path = node_modules_path
        self.timeout = timeout
        self.bridge_script = None
        self._find_bridge_script()
        self._verify_dependencies()
        
        # Cache for parsed files
        self._cache = {}
        self._cache_lock = threading.Lock()
        
    def _find_bridge_script(self):
        """Find the TypeScript bridge script."""
        # Look for the bridge script relative to this file
        current_dir = Path(__file__).parent
        bridge_path = current_dir / "ts_bridge.js"
        
        if bridge_path.exists():
            self.bridge_script = str(bridge_path)
            log_debug(f"Found TypeScript bridge at: {self.bridge_script}")
        else:
            raise AnalysisError(f"TypeScript bridge script not found at: {bridge_path}")
    
    def _verify_dependencies(self):
        """Verify Node.js and TypeScript dependencies."""
        try:
            # Check Node.js
            result = subprocess.run(['node', '--version'], 
                                  capture_output=True, text=True, timeout=5)
            if result.returncode != 0:
                raise AnalysisError("Node.js is not installed or not in PATH")
            
            log_debug(f"Node.js version: {result.stdout.strip()}")
            
            # Check TypeScript availability
            result = subprocess.run(['node', '-e', 'require("typescript"); console.log("OK")'],
                                  capture_output=True, text=True, timeout=5)
            if result.returncode != 0:
                raise AnalysisError("TypeScript is not installed. Run: npm install -g typescript")
            
            log_debug("TypeScript dependency verified")
            
        except subprocess.TimeoutExpired:
            raise AnalysisError("Timeout verifying Node.js/TypeScript dependencies")
        except FileNotFoundError:
            raise AnalysisError("Node.js is not installed or not in PATH")
    
    def parse_file(self, file_path: str, tsconfig_path: Optional[str] = None, 
                   use_cache: bool = True) -> Dict[str, Any]:
        """Parse a TypeScript/JavaScript file and return AST information."""
        file_path = str(Path(file_path).resolve())
        
        # Check cache first
        cache_key = f"{file_path}:{tsconfig_path}:{os.path.getmtime(file_path)}"
        
        if use_cache:
            with self._cache_lock:
                if cache_key in self._cache:
                    log_debug(f"Using cached result for: {file_path}")
                    return self._cache[cache_key]
        
        try:
            # Prepare command
            cmd = ['node', self.bridge_script, 'parse', file_path]
            if tsconfig_path:
                cmd.append(str(Path(tsconfig_path).resolve()))
            
            log_debug(f"Running TypeScript parser: {' '.join(cmd)}")
            
            # Execute the Node.js bridge
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=self.timeout,
                cwd=Path(self.bridge_script).parent
            )
            
            if result.returncode != 0:
                error_msg = result.stderr.strip() or result.stdout.strip()
                raise AnalysisError(f"TypeScript parser failed: {error_msg}")
            
            # Parse the JSON response
            try:
                response = json.loads(result.stdout)
            except json.JSONDecodeError as e:
                raise AnalysisError(f"Invalid JSON response from TypeScript parser: {e}")
            
            if not response.get('success', False):
                error_msg = response.get('error', 'Unknown error')
                raise AnalysisError(f"TypeScript parsing failed: {error_msg}")
            
            # Cache the result
            if use_cache:
                with self._cache_lock:
                    self._cache[cache_key] = response
                    # Limit cache size
                    if len(self._cache) > 100:
                        # Remove oldest entries
                        old_keys = list(self._cache.keys())[:20]
                        for key in old_keys:
                            del self._cache[key]
            
            log_info(f"Successfully parsed TypeScript file: {file_path}")
            return response
            
        except subprocess.TimeoutExpired:
            raise AnalysisError(f"TypeScript parsing timed out after {self.timeout}s")
        except Exception as e:
            raise AnalysisError(f"Error parsing TypeScript file: {str(e)}")
    
    def parse_multiple_files(self, file_paths: List[str], 
                           tsconfig_path: Optional[str] = None) -> Dict[str, Dict[str, Any]]:
        """Parse multiple TypeScript/JavaScript files."""
        results = {}
        
        for file_path in file_paths:
            try:
                result = self.parse_file(file_path, tsconfig_path)
                results[file_path] = result
            except Exception as e:
                log_info(f"Failed to parse {file_path}: {e}")
                results[file_path] = {
                    'success': False,
                    'error': str(e),
                    'fileName': file_path
                }
        
        return results
    
    def get_type_information(self, file_path: str, line: int, column: int,
                           tsconfig_path: Optional[str] = None) -> Dict[str, Any]:
        """Get type information at a specific location in a TypeScript file."""
        # This would require extending the bridge script to support type queries
        # For now, return basic information
        try:
            parsed = self.parse_file(file_path, tsconfig_path)
            
            # Find the closest symbol at the given location
            closest_symbol = None
            min_distance = float('inf')
            
            for symbol_list in [parsed.get('functions', []), 
                              parsed.get('variables', []),
                              parsed.get('classes', [])]:
                for symbol in symbol_list:
                    symbol_line = symbol.get('line', 0)
                    symbol_col = symbol.get('column', 0)
                    
                    # Calculate distance (simple Manhattan distance)
                    distance = abs(line - symbol_line) + abs(column - symbol_col)
                    
                    if distance < min_distance:
                        min_distance = distance
                        closest_symbol = symbol
            
            return {
                'success': True,
                'line': line,
                'column': column,
                'closest_symbol': closest_symbol,
                'distance': min_distance
            }
            
        except Exception as e:
            return {
                'success': False,
                'error': str(e)
            }
    
    def resolve_module(self, module_name: str, from_file: str,
                      tsconfig_path: Optional[str] = None) -> Dict[str, Any]:
        """Resolve a module import to its file path."""
        try:
            from_path = Path(from_file).parent
            
            # Basic module resolution logic
            if module_name.startswith('./') or module_name.startswith('../'):
                # Relative import
                resolved_path = (from_path / module_name).resolve()
                
                # Try different extensions
                for ext in ['.ts', '.tsx', '.js', '.jsx', '.d.ts']:
                    if resolved_path.with_suffix(ext).exists():
                        return {
                            'success': True,
                            'resolved_path': str(resolved_path.with_suffix(ext)),
                            'is_relative': True
                        }
                
                # Try as directory with index file
                for ext in ['.ts', '.tsx', '.js', '.jsx']:
                    index_file = resolved_path / f'index{ext}'
                    if index_file.exists():
                        return {
                            'success': True,
                            'resolved_path': str(index_file),
                            'is_relative': True
                        }
            
            else:
                # Absolute import - look in node_modules
                node_modules_paths = []
                
                # Add node_modules from current directory up to root
                current = from_path
                while current.parent != current:
                    node_modules_paths.append(current / 'node_modules')
                    current = current.parent
                
                # Add global node_modules if specified
                if self.node_modules_path:
                    node_modules_paths.append(Path(self.node_modules_path))
                
                for node_modules in node_modules_paths:
                    if not node_modules.exists():
                        continue
                    
                    module_path = node_modules / module_name
                    
                    # Try package.json main field
                    package_json = module_path / 'package.json'
                    if package_json.exists():
                        try:
                            with open(package_json, 'r') as f:
                                package_data = json.load(f)
                            
                            main_field = package_data.get('main', 'index.js')
                            main_file = module_path / main_field
                            
                            if main_file.exists():
                                return {
                                    'success': True,
                                    'resolved_path': str(main_file),
                                    'is_relative': False,
                                    'package_json': str(package_json)
                                }
                        except (json.JSONDecodeError, IOError):
                            pass
                    
                    # Try index files
                    for ext in ['.ts', '.tsx', '.js', '.jsx', '.d.ts']:
                        index_file = module_path / f'index{ext}'
                        if index_file.exists():
                            return {
                                'success': True,
                                'resolved_path': str(index_file),
                                'is_relative': False
                            }
            
            return {
                'success': False,
                'error': f'Could not resolve module: {module_name}'
            }
            
        except Exception as e:
            return {
                'success': False,
                'error': f'Error resolving module: {str(e)}'
            }
    
    def get_project_files(self, tsconfig_path: str) -> List[str]:
        """Get all TypeScript/JavaScript files in a project based on tsconfig.json."""
        try:
            tsconfig_path = Path(tsconfig_path)
            
            if not tsconfig_path.exists():
                raise AnalysisError(f"tsconfig.json not found: {tsconfig_path}")
            
            with open(tsconfig_path, 'r') as f:
                config = json.load(f)
            
            project_dir = tsconfig_path.parent
            files = []
            
            # Get include patterns
            include_patterns = config.get('include', ['**/*.ts', '**/*.tsx'])
            exclude_patterns = config.get('exclude', ['node_modules/**'])
            
            # Basic glob pattern matching (simplified)
            for pattern in include_patterns:
                if '**' in pattern:
                    # Recursive search
                    ext = pattern.split('.')[-1]
                    for file_path in project_dir.rglob(f'*.{ext}'):
                        # Check if file should be excluded
                        relative_path = file_path.relative_to(project_dir)
                        should_exclude = False
                        
                        for exclude_pattern in exclude_patterns:
                            if exclude_pattern.replace('**/', '') in str(relative_path):
                                should_exclude = True
                                break
                        
                        if not should_exclude:
                            files.append(str(file_path))
                else:
                    # Direct file pattern
                    file_path = project_dir / pattern
                    if file_path.exists():
                        files.append(str(file_path))
            
            # Add explicit files
            explicit_files = config.get('files', [])
            for file_path in explicit_files:
                full_path = project_dir / file_path
                if full_path.exists():
                    files.append(str(full_path))
            
            return sorted(list(set(files)))
            
        except Exception as e:
            raise AnalysisError(f"Error reading TypeScript project: {str(e)}")
    
    def clear_cache(self):
        """Clear the parsing cache."""
        with self._cache_lock:
            self._cache.clear()
            log_debug("TypeScript parser cache cleared")
    
    def get_cache_stats(self) -> Dict[str, Any]:
        """Get cache statistics."""
        with self._cache_lock:
            return {
                'cache_size': len(self._cache),
                'cache_keys': list(self._cache.keys())
            }
    
    def validate_syntax(self, file_path: str) -> Dict[str, Any]:
        """Validate TypeScript/JavaScript syntax without full parsing."""
        try:
            # Use a lightweight parsing approach
            result = self.parse_file(file_path, use_cache=False)
            
            return {
                'valid': result.get('success', False),
                'file_path': file_path,
                'errors': [] if result.get('success') else [result.get('error', 'Unknown error')]
            }
            
        except Exception as e:
            return {
                'valid': False,
                'file_path': file_path,
                'errors': [str(e)]
            }


class TypeScriptProjectAnalyzer:
    """Analyzer for entire TypeScript projects."""
    
    def __init__(self, compiler: TypeScriptCompiler):
        """Initialize with a TypeScript compiler instance."""
        self.compiler = compiler
    
    def analyze_project(self, project_root: str, 
                       tsconfig_name: str = 'tsconfig.json') -> Dict[str, Any]:
        """Analyze an entire TypeScript project."""
        project_root = Path(project_root)
        tsconfig_path = project_root / tsconfig_name
        
        if not tsconfig_path.exists():
            raise AnalysisError(f"No {tsconfig_name} found in project root: {project_root}")
        
        try:
            # Get all project files
            files = self.compiler.get_project_files(str(tsconfig_path))
            
            log_info(f"Analyzing TypeScript project with {len(files)} files")
            
            # Parse all files
            parsed_files = self.compiler.parse_multiple_files(files, str(tsconfig_path))
            
            # Aggregate results
            total_functions = 0
            total_classes = 0
            total_interfaces = 0
            total_imports = 0
            errors = []
            
            for file_path, result in parsed_files.items():
                if result.get('success'):
                    total_functions += len(result.get('functions', []))
                    total_classes += len(result.get('classes', []))
                    total_interfaces += len(result.get('interfaces', []))
                    total_imports += len(result.get('imports', []))
                else:
                    errors.append({
                        'file': file_path,
                        'error': result.get('error', 'Unknown error')
                    })
            
            return {
                'success': True,
                'project_root': str(project_root),
                'tsconfig_path': str(tsconfig_path),
                'total_files': len(files),
                'successful_parses': len([r for r in parsed_files.values() if r.get('success')]),
                'failed_parses': len([r for r in parsed_files.values() if not r.get('success')]),
                'statistics': {
                    'total_functions': total_functions,
                    'total_classes': total_classes,
                    'total_interfaces': total_interfaces,
                    'total_imports': total_imports
                },
                'errors': errors,
                'parsed_files': parsed_files
            }
            
        except Exception as e:
            return {
                'success': False,
                'error': str(e),
                'project_root': str(project_root)
            }